﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using SecureConnection;

namespace RemoteCode
{
    public class SendQueue
    {
        public int MaxQueueSize { get; set; }
        public delegate byte[] PrepareDataCallback(byte[] data, object obj);

        private ManualResetEvent _mrEntryDequeued = new ManualResetEvent(false);
        private ManualResetEvent _mrEntryEnqueued = new ManualResetEvent(false);
        private ManualResetEvent _mrWorkerTerminated = new ManualResetEvent(false);

        private bool _running = true;

        private class QueueEntry
        {
            public QueueEntry(byte[] data, PrepareDataCallback callback, object userstate)
            {
                this.data = data;
                this.callback = callback;
                this.userstate = userstate;
            }

            public byte[] data;
            public PrepareDataCallback callback;
            public object userstate;
        }

        private Connection _conn;
        private Queue<QueueEntry> _queue = new Queue<QueueEntry>();

        public SendQueue(Connection conn)
        {
            _conn = conn;

            this.MaxQueueSize = 25;

            ThreadPool.QueueUserWorkItem(new WaitCallback((object obj) => Worker()));
        }

        public void Enqueue(byte[] data, PrepareDataCallback callback, object userstate)
        {
            while (_queue.Count > MaxQueueSize)
            {
                _mrEntryDequeued.WaitOne();
                _mrEntryDequeued.Reset();
            }

            lock (_queue)
            {
                _queue.Enqueue(new QueueEntry(data, callback, userstate));
                _mrEntryEnqueued.Set();
            }
        }

        private void Worker()
        {
            QueueEntry entry = null;
            while (_running)
            {
                if(_queue.Count < 1)
                {
                    _mrEntryEnqueued.WaitOne();
                    _mrEntryEnqueued.Reset();
                }

                while (_running && _queue.Count > 0)
                {
                    lock (_queue)
                    {
                        entry = _queue.Dequeue();
                        _mrEntryDequeued.Set();
                    }

                    entry.data = entry.callback(entry.data, entry.userstate);

                    _conn.Send(entry.data);

                    entry.data = null;
                    entry.userstate = null;
                }
            }
            _mrWorkerTerminated.Set();
        }

        public void Stop()
        {
            _running = false;
            _mrEntryEnqueued.Set();
            _mrWorkerTerminated.WaitOne();
        }
    }
}
